/*
 * (C) Copyright 2007-2012
 * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
 *
 * Description: MMC driver for winner's mmc controller
 * Author: Aaron <leafy.myeh@allwinnertech.com>
 * Date: 2012-2-3 14:18:18
 */
#include "common.h"
#include "mmc_def.h"
#include "mmc_bsp.h"
#include "mmc_host_0_3_v4px.h"
#include "mmc.h"
#include <asm/arch/gpio.h>
#include <private_boot0.h>
#include <private_toc.h>
#include <private_uboot.h>


//extern const boot0_file_head_t BT0_head;
extern int mmc_config_addr;
extern int mmc_register(int dev_num, struct mmc *mmc);
extern int mmc_unregister(int dev_num);

extern struct mmc mmc_dev[MAX_MMC_NUM];
extern struct sunxi_mmc_host mmc_host[MAX_MMC_NUM];


/* delay control */
#define SDXC_StartCal        (1<<15)
#define SDXC_CalDone         (1<<14)
#define SDXC_CalDly          (0x3F<<8)
#define SDXC_EnableDly       (1<<7)
#define SDXC_CfgDly          (0x3F<<0)


static int mmc_update_clk(struct mmc *mmc)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v4px *reg = (struct mmc_reg_v4px *)mmchost->reg;
    unsigned int cmd;
    int timeout = 0xfffff;

    cmd = (1U << 31) | (1 << 21) | (1 << 13);
    writel(cmd, &reg->cmd);
    while((readl(&reg->cmd)&0x80000000) && timeout--);
    if (timeout<0){
        mmcinfo("mmc %d update clk failed\n", mmchost->mmc_no);
        writel(0x0, 0x1c2084c);
        mmcinfo("0x%x 0x%x\n", readl(0x1c2084c), readl(0x1c20858));
        dumphex32("mmc", (char*)mmchost->reg, 0x100);
        return -1;
    }

    writel(readl(&reg->rint), &reg->rint);
    return 0;
}

static int mmc_update_phase(struct mmc *mmc)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;

    if (mmchost->timing_mode == SUNXI_MMC_TIMING_MODE_1)
    {
        mmcdbg("mmc re-update_phase\n");
        return mmc_update_clk(mmc);
    }

    return 0;
}

/*
   -- speed mode --
sm0: DS26_SDR12
sm1: HSSDR52_SDR25
sm2: HSDDR52_DDR50
sm3: HS200_SDR104
sm4: HS400

-- frequency point --
f0: CLK_400K
f1: CLK_25M
f2: CLK_50M
f3: CLK_100M
f4: CLK_150M
f5: CLK_200M
*/
/* some information is passed in boot0's extend data buffer: BT0_head.prvt_head.storage_data
   these information is write to BT0_head.prvt_head.storage_data before write boot0 during product.
   1. BT0_head.prvt_head.storage_data + 128 Byte
   +0 struct tune_sdly
   2. BT0_head.prvt_head.storage_data + 128 + sizeof(struct tune_sdly) Byte
   +0 Byte: boot0_para
   +1 Byte: boot_odly_50M
   +2 Byte: boot_sdly_50M
   +3 Byte: boot_odly_50M_ddr
   +4 Byte: boot_sdly_50M_ddr
   +5 Byte: boot_hs_f_max
   */
#define BOOT0_PARA_USE_INTERNAL_DEFAULT_TIMING_PARA (1U<<0)
/* 0: pass through struct sdly;  1: pass through boot_odly/sdly_* */
#define BOOT0_PARA_USE_EXTERNAL_INPUT_TIMING_PARA   (1U<<1)
static int mmc_get_timing_cfg_tm4(u32 sdc_no, u32 spd_md_id, u32 freq_id, u8 *odly, u8 *sdly)
{
    s32 ret = 0;
    struct boot_sdmmc_private_info_t *priv_info =
        (struct boot_sdmmc_private_info_t *)(mmc_config_addr + SDMMC_PRIV_INFO_ADDR_OFFSET);
    struct tune_sdly *tune_sdly = &(priv_info->tune_sdly);
    u8 boot0_para = priv_info->boot_mmc_cfg.boot0_para;
    u32 spd_md_sdly = 0;
    u32 dly = 0;

    if (boot0_para & BOOT0_PARA_USE_INTERNAL_DEFAULT_TIMING_PARA)
    {
        if (spd_md_id == DS26_SDR12)
        {
            if (freq_id <= CLK_25M) {
                *odly = 0;
                *sdly = 2;
            } else {
                mmcinfo("wrong freq %d at spd md %d\n", freq_id, spd_md_id);
                ret = -1;
            }
        }
        else if (spd_md_id == HSSDR52_SDR25)
        {
            if (freq_id <= CLK_25M) {
                *odly = 0;
                *sdly = 3;
            } else if (freq_id == CLK_50M) {
                *odly = 0;
                *sdly = 4;
            } else {
                mmcinfo("wrong freq %d at spd md %d\n", freq_id, spd_md_id);
                ret = -1;
            }
        }
        else
        {
            mmcinfo("wrong speed mode %d\n", spd_md_id);
            ret = -1;
        }
    }
    else
    {
        if (boot0_para & BOOT0_PARA_USE_EXTERNAL_INPUT_TIMING_PARA)
        {
            if (spd_md_id == DS26_SDR12)
            {
                if (freq_id <= CLK_25M) {
                    *odly = 0;
                    *sdly = 2;
                } else {
                    mmcinfo("wrong freq %d at spd md %d\n", freq_id, spd_md_id);
                    ret = -1;
                }
            }
            else if (spd_md_id == HSSDR52_SDR25)
            {
                if (freq_id <= CLK_25M) {
                    *odly = 0;
                    *sdly = 3;
                } else if (freq_id == CLK_50M) {
                    *odly = priv_info->boot_mmc_cfg.boot_odly_50M;
                    *sdly = priv_info->boot_mmc_cfg.boot_sdly_50M;
                } else {
                    mmcinfo("wrong freq %d at spd md %d\n", freq_id, spd_md_id);
                    ret = -1;
                }
            }
            else if (spd_md_id == HSDDR52_DDR50)
            {
                if (freq_id <= CLK_25M) {
                    *odly = 0;
                    *sdly = 2;
                } else if (freq_id == CLK_50M) {
                    *odly = priv_info->boot_mmc_cfg.boot_odly_50M_ddr;
                    *sdly = priv_info->boot_mmc_cfg.boot_sdly_50M_ddr;
                } else {
                    mmcinfo("wrong freq %d at spd md %d\n", freq_id, spd_md_id);
                    ret = -1;
                }
            }
            else
            {
                mmcinfo("wrong speed mode %d\n", spd_md_id);
                ret = -1;
            }
        }
        else
        {
            if ((sdc_no != 2) || (spd_md_id>4) || (freq_id>5)) {
                mmcinfo("wrong input para: mmc %d/2, mode %d/[0,4], freq %d/[0,5]\n", sdc_no, spd_md_id, freq_id);
                ret = -1;
                goto out;
            }

            spd_md_sdly = tune_sdly->tm4_smx_fx[spd_md_id*2 + freq_id/4];
            dly = ((spd_md_sdly>>((freq_id%4)*8)) & 0xff);

            if (dly == 0xff)
            {
                if (spd_md_id == DS26_SDR12)
                {
                    if (freq_id <= CLK_25M) {
                        dly = 0;
                    } else {
                        mmcinfo("wrong freq %d at spd md %d\n", freq_id, spd_md_id);
                        ret = -1;
                    }
                }
                else if (spd_md_id == HSSDR52_SDR25)
                {
                    if (freq_id <= CLK_25M) {
                        dly = 0;
                    } else if (freq_id == CLK_50M){
                        dly = 15;
                    } else {
                        mmcinfo("wrong freq %d at spd md %d\n", freq_id, spd_md_id);
                        ret = -1;
                    }
                }
                else if (spd_md_id == HSDDR52_DDR50)
                {
                    if (freq_id <= CLK_25M) {
                        dly = 0;
                    } else {
                        mmcinfo("wrong freq %d at spd md %d\n", freq_id, spd_md_id);
                        ret = -1;
                    }
                }
                else
                {
                    mmcinfo("wrong speed mode %d\n", spd_md_id);
                    ret = -1;
                }
            }

            *odly = 0;
            *sdly = dly;
            mmcdbg("%s: %d %d 0x%x 0x%x, odly %d sdly %d\n", __FUNCTION__, spd_md_id, freq_id, spd_md_sdly, dly, *odly, *sdly);
        }
    }

out:
    return ret;
}

static int mmc_get_timing_cfg(u32 sdc_no, u32 spd_md_id, u32 freq_id, u8 *odly, u8 *sdly)
{
    s32 ret = 0;
    u32 tm = mmc_host[sdc_no].timing_mode;

    if ((sdc_no == 2) && (tm == SUNXI_MMC_TIMING_MODE_4))
        return mmc_get_timing_cfg_tm4(sdc_no, spd_md_id, freq_id, odly, sdly);
    else if ((sdc_no == 0) && (tm == SUNXI_MMC_TIMING_MODE_1)) {
        if ((spd_md_id <= HSSDR52_SDR25) && (freq_id<=CLK_50M)) {
            *odly = 0;
            *sdly = 0;
            ret = 0;
        } else {
            mmcinfo("sdc0 spd mode error, %d\n", spd_md_id);
            ret = -1;
        }
    } else {
        mmcinfo("mmc_get_timing_cfg: input para error!\n");
        ret = -1;
    }

    return ret;
}

static unsigned mmc_config_delay(struct sunxi_mmc_host* mmchost, u32 spd_md_id, u32 freq_id)
{
    int ret = 0;

    writel(readl(mmchost->mclkbase) & (~(7<<8)),mmchost->mclkbase);
    writel(readl(mmchost->mclkbase) & (~(7<<20)),mmchost->mclkbase);
    writel(readl(mmchost->mclkbase) | (3<<8),mmchost->mclkbase);
    writel(readl(mmchost->mclkbase) | (4<<20),mmchost->mclkbase);

    return ret;

}

static int mmc_config_clock_modex(struct sunxi_mmc_host* mmchost, unsigned clk)
{
    struct mmc_reg_v4px *reg = (struct mmc_reg_v4px *)mmchost->reg;
    unsigned rval = 0;
    struct mmc *mmc = mmchost->mmc;
    unsigned mode = mmchost->timing_mode;
    unsigned freq;

    /* disable mclk */
    writel(0x0, mmchost->mclkbase);
    mmcdbg("mmc %d mclkbase 0x%x\n", mmchost->mmc_no, readl(mmchost->mclkbase));
    mmchost->mod_clk = clk;
    mmc_set_mclk(mmchost, mmchost->mod_clk);
    mmc->clock = mmc_get_mclk(mmchost);
    mmchost->clock = mmc->clock; /* bankup current clock frequency at host */
    mmcdbg("get round card clk %d, mod_clk %d\n", mmc->clock, mmchost->mod_clk);
    /* re-enable mclk */
    writel(readl(mmchost->mclkbase)|(1<<31),mmchost->mclkbase);
    mmcdbg("mmc %d mclkbase 0x%x\n", mmchost->mmc_no, readl(mmchost->mclkbase));

#ifndef CONFIG_FPGA
    rval = readl(&reg->clkcr);
    rval &= ~(0xFF);
    if ((mode == SUNXI_MMC_TIMING_MODE_1)||(mode == SUNXI_MMC_TIMING_MODE_3)) {
        if (mmc->speed_mode == HSDDR52_DDR50)
            rval |= 0x1;
    } else if (mode == SUNXI_MMC_TIMING_MODE_4) {
        if ((mmc->speed_mode == HSDDR52_DDR50) && (mmc->bus_width == 8))
            rval |= 0x1;
    }
    writel(rval, &reg->clkcr);
#else
    if(mmc->speed_mode == HSDDR52_DDR50){
        /* clear internal divider */
        rval = readl(&reg->clkcr);
        rval &= ~0xff;
        rval |= 1;
    }else{
        /* support internal divide clock under fpga environment  */
        rval = readl(&reg->clkcr);
        rval &= ~0xff;
        rval |= 24000000 / mmchost->mod_clk / 2; // =24M/400K/2=0x1E
    }
    writel(rval, &reg->clkcr);
#endif

    if (mmc_update_clk(mmc))
        return -1;

    /* configure delay for current frequency and speed mode */
    if (clk <= 400000)
        freq = CLK_400K;
    else if (clk <= 26000000)
        freq = CLK_25M;
    else if (clk <= 52000000)
        freq = CLK_50M;
    else if (clk <= 100000000)
        freq = CLK_100M;
    else if (clk <= 150000000)
        freq = CLK_150M;
    else if (clk <= 200000000)
        freq = CLK_200M;
    else
        freq = CLK_25M;

    mmc_config_delay(mmchost, mmc->speed_mode, freq);

    return 0;
}

static int mmc_config_clock(struct mmc *mmc, unsigned clk)
{
    unsigned rval = 0;
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host* )mmc->priv;
    struct mmc_reg_v4px *reg = (struct mmc_reg_v4px *)mmchost->reg;

    if ((mmc->speed_mode == HSDDR52_DDR50) || (mmc->speed_mode == HS400)) {
        if (clk > mmc->f_max_ddr)
            clk = mmc->f_max_ddr;
    }

    /* disable card clock */
    rval = readl(&reg->clkcr);
    rval &= ~(1 << 16);
    writel(rval, &reg->clkcr);
    if (mmc_update_clk(mmchost->mmc))
        return -1;

    if ((mmchost->timing_mode == SUNXI_MMC_TIMING_MODE_1)
            || (mmchost->timing_mode == SUNXI_MMC_TIMING_MODE_3)
            || (mmchost->timing_mode == SUNXI_MMC_TIMING_MODE_4) )

        mmc_config_clock_modex(mmchost, clk);

    else {
        mmcinfo("mmc %d wrong timing mode: 0x%x\n",
                mmchost->mmc_no, mmchost->timing_mode);
        return -1;
    }

    /* Re-enable card clock */
    rval = readl(&reg->clkcr);
    rval |=  (0x1 << 16); //(3 << 16);
    writel(rval, &reg->clkcr);
    if (mmc_update_clk(mmchost->mmc)){
        mmcinfo("mmc %d re-enable clock failed\n",mmchost->mmc_no);
        return -1;
    }

    return 0;
}

static void mmc_ddr_mode_onoff(struct mmc *mmc, int on)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v4px *reg = (struct mmc_reg_v4px *)mmchost->reg;
    u32 rval = 0;

    rval = readl(&reg->gctrl);
    rval &= (~(1U << 10));

    if (on) {
        rval |= (1U << 10);
        writel(rval, &reg->gctrl);
        mmcdbg("set %d rgctrl 0x%x to enable ddr mode\n", mmchost->mmc_no, readl(&reg->gctrl));
    } else {
        writel(rval, &reg->gctrl);
        mmcdbg("set %d rgctrl 0x%x to disable ddr mode\n", mmchost->mmc_no, readl(&reg->gctrl));
    }
}

static void mmc_hs400_mode_onoff(struct mmc *mmc, int on)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v4px *reg = (struct mmc_reg_v4px *)mmchost->reg;
    u32 rval = 0;

    if (mmchost->mmc_no != 2) {
        return ;
    }

    rval = readl(&reg->dsbd);
    rval &= (~(1 << 31));

    if (on) {
        rval |= (1 << 31);
        writel(rval, &reg->dsbd);
        mmcdbg("set %d dsbd 0x%x to enable hs400 mode\n", mmchost->mmc_no, readl(&reg->dsbd));
    } else {
        writel(rval, &reg->dsbd);
        mmcdbg("set %d dsbd 0x%x to disable hs400 mode\n", mmchost->mmc_no, readl(&reg->dsbd));
    }
}


static void mmc_set_ios(struct mmc *mmc)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v4px *reg = (struct mmc_reg_v4px *)mmchost->reg;


    mmcdbg("mmc %d ios: bus: %d, clock: %d\n",mmchost ->mmc_no, mmc->bus_width, mmc->clock);

    if (mmc->clock && mmc_config_clock(mmc, mmc->clock)) {
        mmcinfo("[mmc]: " "*** update clock failed\n");
        mmchost->fatal_err = 1;
        return;
    }
    /* Change bus width */
    if (mmc->bus_width == 8)
        writel(2, &reg->width);
    else if (mmc->bus_width == 4)
        writel(1, &reg->width);
    else
        writel(0, &reg->width);

    /* set ddr mode */
    if (mmc->speed_mode == HSDDR52_DDR50) {
        mmc_ddr_mode_onoff(mmc, 1);
        mmc_hs400_mode_onoff(mmc, 0);
    } else if (mmc->speed_mode == HS400) {
        mmc_ddr_mode_onoff(mmc, 0);
        mmc_hs400_mode_onoff(mmc, 1);
    } else {
        mmc_ddr_mode_onoff(mmc, 0);
        mmc_hs400_mode_onoff(mmc, 0);
    }
}

static int mmc_core_init(struct mmc *mmc)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v4px *reg = (struct mmc_reg_v4px *)mmchost->reg;

    /* Reset controller */
    writel(0x7, &reg->gctrl);
    while(readl(&reg->gctrl)&0x7);

#define  SMC_DATA_TIMEOUT     0xffffffU
#define  SMC_RESP_TIMEOUT     0xff

    writel((SMC_DATA_TIMEOUT<<8)|SMC_RESP_TIMEOUT, &reg->timeout); //Set Data & Response Timeout Value

    writel((512<<16)|(1U<<0), &reg->thldc);

    writel(0xdeb, &reg->dbgc);

    /* release eMMC reset signal */
    writel(1, &reg->hwrst);
    writel(0, &reg->hwrst);
    __msdelay(1);
    writel(1, &reg->hwrst);
    __msdelay(1);

    writel(readl(mmchost->mclkbase) & (~(7<<8)),mmchost->mclkbase);
    writel(readl(mmchost->mclkbase) & (~(7<<20)),mmchost->mclkbase);
    writel(readl(mmchost->mclkbase) | (3<<8),mmchost->mclkbase);
    writel(readl(mmchost->mclkbase) | (4<<20),mmchost->mclkbase);

    return 0;
}

static int mmc_trans_data_by_cpu(struct mmc *mmc, struct mmc_data *data)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v4px *reg = (struct mmc_reg_v4px *)mmchost->reg;
    unsigned i;
    unsigned byte_cnt = data->blocksize * data->blocks;
    unsigned *buff;
    unsigned timeout = 0xffffff;

    if (data->flags & MMC_DATA_READ) {
        buff = (unsigned int *)data->b.dest;
        for (i=0; i<(byte_cnt>>2); i++) {
            while(--timeout && (readl(&reg->status)&(1 << 2)));
            if (timeout <= 0)
                goto out;
            buff[i] = readl(mmchost->database);
            timeout = 0xffffff;
        }
    } else {
        buff = (unsigned int *)data->b.src;
        for (i=0; i<(byte_cnt>>2); i++) {
            while(--timeout && (readl(&reg->status)&(1 << 3)));
            if (timeout <= 0)
                goto out;
            writel(buff[i], mmchost->database);
            timeout = 0xffffff;
        }
    }

out:
    if (timeout <= 0){
        mmcinfo("mmc %d transfer by cpu failed\n",mmchost ->mmc_no);
        return -1;
    }

    return 0;
}

static int mmc_trans_data_by_dma(struct mmc *mmc, struct mmc_data *data)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v4px *reg = (struct mmc_reg_v4px *)mmchost->reg;
    struct mmc_des_v4px *pdes = NULL;
    unsigned byte_cnt = data->blocksize * data->blocks;
    unsigned char *buff;
    unsigned des_idx = 0;
    unsigned buff_frag_num = 0;
    unsigned remain;
    unsigned i, rval;

    buff = data->flags & MMC_DATA_READ ?
        (unsigned char *)data->b.dest : (unsigned char *)data->b.src;
    buff_frag_num = byte_cnt >> SMHC_V4PX_DES_NUM_SHIFT;
    remain = byte_cnt & (SMHC_V4PX_DES_BUFFER_MAX_LEN-1);
    if (remain)
        buff_frag_num ++;
    else
        remain = SMHC_V4PX_DES_BUFFER_MAX_LEN;

    pdes = mmchost->pdes;

    //OSAL_CacheRangeFlush(buff, (unsigned long)byte_cnt, CACHE_CLEAN_FLUSH_D_CACHE_REGION);
    for (i=0; i < buff_frag_num; i++, des_idx++) {
        memset((void*)&pdes[des_idx], 0, sizeof(struct mmc_des_v4px));
        pdes[des_idx].des_chain = 1;
        pdes[des_idx].own = 1;
        pdes[des_idx].dic = 1;
        if (buff_frag_num > 1 && i != buff_frag_num-1) {
            pdes[des_idx].data_buf1_sz = SMHC_V4PX_DES_BUFFER_MAX_LEN;
        } else
            pdes[des_idx].data_buf1_sz = remain;

        pdes[des_idx].buf_addr_ptr1 = (u32)buff + i * SMHC_V4PX_DES_BUFFER_MAX_LEN;
        if (i==0)
            pdes[des_idx].first_des = 1;

        if (i == buff_frag_num-1) {
            pdes[des_idx].dic = 0;
            pdes[des_idx].last_des = 1;
            pdes[des_idx].end_of_ring = 1;
            pdes[des_idx].buf_addr_ptr2 = 0;
        } else {
            pdes[des_idx].buf_addr_ptr2 = (u32)&pdes[des_idx+1];
        }
        mmcdbg("mmc %d frag %d, remain %d, des[%d](%x): "
                "[0] = %x, [1] = %x, [2] = %x, [3] = %x\n",mmchost ->mmc_no,
                i, remain, des_idx, (u32)&pdes[des_idx],
                (u32)((u32*)&pdes[des_idx])[0], (u32)((u32*)&pdes[des_idx])[1],
                (u32)((u32*)&pdes[des_idx])[2], (u32)((u32*)&pdes[des_idx])[3]);
    }
    //OSAL_CacheRangeFlush(pdes, sizeof(struct sunxi_mmc_des) * (des_idx+1), CACHE_CLEAN_FLUSH_D_CACHE_REGION);

    /*
     * GCTRLREG
     * GCTRL[2] : DMA reset
     * GCTRL[5] : DMA enable
     *
     * IDMACREG
     * IDMAC[0] : IDMA soft reset
     * IDMAC[1] : IDMA fix burst flag
     * IDMAC[7] : IDMA on
     *
     * IDIECREG
     * IDIE[0]  : IDMA transmit interrupt flag
     * IDIE[1]  : IDMA receive interrupt flag
     */
    rval = readl(&reg->gctrl);
    writel(rval|(1 << 5)|(1 << 2), &reg->gctrl);    /* dma enable */
    writel((1 << 0), &reg->dmac); /* idma reset */
    writel((1 << 1) | (1 << 7), &reg->dmac); /* idma on */
    rval = readl(&reg->idie) & (~3);
    if (data->flags & MMC_DATA_WRITE)
        rval |= (1 << 0);
    else
        rval |= (1 << 1);
    writel(rval, &reg->idie);
    writel((u32)pdes, &reg->dlba);
    writel((2U<<28)|(7<<16)|248, &reg->ftrglevel); /* burst-8, rx/tx trigger level=7/248 */

    return 0;
}

static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
        struct mmc_data *data)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v4px *reg = (struct mmc_reg_v4px *)mmchost->reg;
    unsigned int cmdval = 0x80000000;
    unsigned int timeout = 0;
    int error = 0;
    unsigned int status = 0;
    unsigned int usedma = 0;
    unsigned int bytecnt = 0;

    if (mmchost->fatal_err){
        mmcinfo("mmc %d Found fatal err,so no send cmd\n",mmchost ->mmc_no);
        return -1;
    }
    if (cmd->resp_type & MMC_RSP_BUSY)
        mmcdbg("mmc %d cmd %d check rsp busy\n",mmchost ->mmc_no, cmd->cmdidx);
    if (cmd->cmdidx == 12)
        return 0;
    /*
     * CMDREG
     * CMD[5:0] : Command index
     * CMD[6]   : Has response
     * CMD[7]   : Long response
     * CMD[8]   : Check response CRC
     * CMD[9]   : Has data
     * CMD[10]  : Write
     * CMD[11]  : Steam mode
     * CMD[12]  : Auto stop
     * CMD[13]  : Wait previous over
     * CMD[14]  : About cmd
     * CMD[15]  : Send initialization
     * CMD[21]  : Update clock
     * CMD[31]  : Load cmd
     */
    if (!cmd->cmdidx)
        cmdval |= (1 << 15);
    if (cmd->resp_type & MMC_RSP_PRESENT)
        cmdval |= (1 << 6);
    if (cmd->resp_type & MMC_RSP_136)
        cmdval |= (1 << 7);
    if (cmd->resp_type & MMC_RSP_CRC)
        cmdval |= (1 << 8);
    if (data) {
        if ((u32)data->b.dest & 0x3) {
            mmcinfo("mmc %d dest is not 4 byte align\n",mmchost ->mmc_no);
            error = -1;
            goto out;
        }

        cmdval |= (1 << 9) | (1 << 13);
        if (data->flags & MMC_DATA_WRITE)
            cmdval |= (1 << 10);
        if (data->blocks > 1)
            cmdval |= (1 << 12);
        writel(data->blocksize, &reg->blksz);
        writel(data->blocks * data->blocksize, &reg->bytecnt);
    }

    mmcdbg("mmc %d, cmd %d(0x%x), arg 0x%x\n", mmchost->mmc_no, cmd->cmdidx, cmdval|cmd->cmdidx, cmd->cmdarg);
    writel(cmd->cmdarg, &reg->arg);
    if (!data)
        writel(cmdval|cmd->cmdidx, &reg->cmd);

    /*
     * transfer data and check status
     * STATREG[2] : FIFO empty
     * STATREG[3] : FIFO full
     */
    if (data) {
        int ret = 0;
        bytecnt = data->blocksize * data->blocks;
        mmcdbg("mmc %d trans data %d bytes\n",mmchost->mmc_no, bytecnt);
#ifdef MMC_TRANS_BY_DMA
        if (bytecnt > 512) {
#else
            if (0) {
#endif
                usedma = 1;
                writel(readl(&reg->gctrl)&(~0x80000000), &reg->gctrl);
                ret = mmc_trans_data_by_dma(mmc, data);
                writel(cmdval|cmd->cmdidx, &reg->cmd);
            } else {
                writel(readl(&reg->gctrl)|0x80000000, &reg->gctrl);
                writel(cmdval|cmd->cmdidx, &reg->cmd);
                ret = mmc_trans_data_by_cpu(mmc, data);
            }
            if (ret) {
                mmcinfo("mmc %d Transfer failed\n", mmchost->mmc_no);
                error = readl(&reg->rint) & 0xbbc2;
                if(!error)
                    error = 0xffffffff;
                goto out;
            }
        }

        timeout = 0xffffff;
        do {
            status = readl(&reg->rint);
            if (!timeout-- || (status & 0xbbc2)) {
                error = status & 0xbbc2;
                if(!error)
                    error = 0xffffffff;//represet software timeout
                mmcinfo("mmc %d cmd %d timeout, err %x\n",mmchost ->mmc_no, cmd->cmdidx, error);
                goto out;
            }
        } while (!(status&0x4));

        if (data) {
            unsigned done = 0;
            timeout = usedma ? 0xffff*bytecnt : 0xffff;

            do {
                status = readl(&reg->rint);
                if (!timeout-- || (status & 0xbbc2)) {
                    error = status & 0xbbc2;
                    if(!error)
                        error = 0xffffffff;//represet software timeout
                    mmcinfo("mmc %d data timeout, err %x\n",mmchost->mmc_no, error);
                    goto out;
                }
                if (data->blocks > 1)
                    done = status & (1 << 14);
                else
                    done = status & (1 << 3);
            } while (!done);

            if ((data->flags & MMC_DATA_READ)&& usedma) {
                timeout = 0xffffff;
                done = 0;
                status = 0;
                mmcdbg("mmc %d cacl rd dma timeout %x\n", mmchost->mmc_no, timeout);
                do {
                    status = readl(&reg->idst);
                    if (!timeout-- || (status & 0x234)) {
                        error = status & 0x1E34;
                        if(!error)
                            error = 0xffffffff; //represet software timeout
                        mmcinfo("mmc %d wait dma over err %x\n", mmchost->mmc_no, error);
                        goto out;
                    }
                    done = status & (1 << 1);
                    //usdelay(1);
                } while (!done);
                mmcdbg("idst *****0x%d******\n",readl(&reg->idst));
            }
        }

        if (cmd->resp_type & MMC_RSP_BUSY) {
            timeout = 0x4ffffff;
            do {
                status = readl(&reg->status);
                if (!timeout--) {
                    error = -1;
                    mmcinfo("mmc %d busy timeout\n",mmchost->mmc_no);
                    goto out;
                }
            } while (status & (1 << 9));
        }
        if (cmd->resp_type & MMC_RSP_136) {
            cmd->response[0] = readl(&reg->resp3);
            cmd->response[1] = readl(&reg->resp2);
            cmd->response[2] = readl(&reg->resp1);
            cmd->response[3] = readl(&reg->resp0);
            mmcdbg("mmc %d resp 0x%x 0x%x 0x%x 0x%x\n",mmchost ->mmc_no,
                    cmd->response[3], cmd->response[2],
                    cmd->response[1], cmd->response[0]);
        } else {
            cmd->response[0] = readl(&reg->resp0);
            mmcdbg("mmc %d resp 0x%x\n",mmchost->mmc_no, cmd->response[0]);
        }
out:
        if (data && usedma) {
            /* IDMASTAREG
             * IDST[0] : idma tx int
             * IDST[1] : idma rx int
             * IDST[2] : idma fatal bus error
             * IDST[4] : idma descriptor invalid
             * IDST[5] : idma error summary
             * IDST[8] : idma normal interrupt sumary
             * IDST[9] : idma abnormal interrupt sumary
             */
            status = readl(&reg->idst);
            writel(status, &reg->idst);
            writel(0, &reg->idie);
            writel(0, &reg->dmac);
            writel(readl(&reg->gctrl)&(~(1 << 5)), &reg->gctrl);
            //OSAL_CacheRangeFlush(data->b.dest, data->blocksize * data->blocks, 4);
        }
        if (error) {
            dumphex32("mmc", (char*)mmchost->reg, 0x200);
            writel(0x7, &reg->gctrl);
            while(readl(&reg->gctrl)&0x7);
            mmc_update_clk(mmc);
            mmcinfo("mmc %d cmd %d err %x\n",mmchost->mmc_no, cmd->cmdidx, error);
        }
        writel(0xffffffff, &reg->rint);

        if (error)
            return -1;
        else
            return 0;
    }

    int mmc_v4px_init(int sdc_no, unsigned bus_width, const normal_gpio_cfg *gpio_info, int offset ,void *extra_data)
    {
        struct mmc *mmc;
        int ret;
        u8 sdly=0xff, odly=0xff;
        struct boot_sdmmc_private_info_t *priv_info =
            (struct boot_sdmmc_private_info_t *)(mmc_config_addr + SDMMC_PRIV_INFO_ADDR_OFFSET);
        u8 ext_f_max = priv_info->boot_mmc_cfg.boot_hs_f_max;

        mmcinfo("mmc driver ver %s\n", DRIVER_VER);

        memset(&mmc_dev[sdc_no], 0, sizeof(struct mmc));
        memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host));
        mmc = &mmc_dev[sdc_no];
        mmc_host[sdc_no].mmc = mmc;

        if ((sdc_no == 0) || (sdc_no == 1))
            mmc_host[sdc_no].timing_mode = SUNXI_MMC_TIMING_MODE_1; //SUNXI_MMC_TIMING_MODE_3
        else if ((sdc_no == 2) || (sdc_no == 3))
            mmc_host[sdc_no].timing_mode = SUNXI_MMC_TIMING_MODE_4;

        strcpy(mmc->name, "SUNXI SD/MMC");
        mmc->priv = &mmc_host[sdc_no];
        mmc->send_cmd = mmc_send_cmd;
        mmc->set_ios = mmc_set_ios;
        mmc->init = mmc_core_init;
        mmc->update_phase = mmc_update_phase;

        mmc->voltages = MMC_VDD_29_30|MMC_VDD_30_31|MMC_VDD_31_32|MMC_VDD_32_33|
            MMC_VDD_33_34|MMC_VDD_34_35|MMC_VDD_35_36;
        mmc->host_caps = MMC_MODE_HS_52MHz|MMC_MODE_HS|MMC_MODE_HC;
        if (bus_width == 4)
            mmc->host_caps |= MMC_MODE_4BIT;
        if (((sdc_no == 2) || (sdc_no == 3)) && (bus_width == 8)) {
            mmc->host_caps |= MMC_MODE_8BIT | MMC_MODE_4BIT;
        }

        mmc->f_min = 400000;
        mmc->f_max = 50000000;
        mmc->f_max_ddr = 50000000;
        if (!mmc_get_timing_cfg(sdc_no, 1, 2, &odly, &sdly)) {
            if (!((odly != 0xff) && (sdly != 0xff)))
                mmc->f_max = 25000000;
        } else
            mmc->f_max = 25000000;

        if ( !mmc_get_timing_cfg(sdc_no, 2, 2, &odly, &sdly) ) {
            if ((odly != 0xff) && (sdly != 0xff))
                mmc->host_caps |= MMC_MODE_DDR_52MHz;
            else
                mmc->f_max_ddr = 25000000;
        } else
            mmc->f_max_ddr = 25000000;

        if (ext_f_max && ((mmc->f_max_ddr/1000000) > ext_f_max))
            mmc->f_max_ddr = ext_f_max * 1000000;
        if (ext_f_max && ((mmc->f_max/1000000) > ext_f_max))
            mmc->f_max = ext_f_max * 1000000;

        mmc->control_num = sdc_no;

        mmc_host[sdc_no].pdes = (struct mmc_des_v4px *)DMAC_DES_BASE_IN_SDRAM;
        if (mmc_resource_init(sdc_no)){
            mmcinfo("mmc %d resource init failed\n",sdc_no);
            return -1;
        }

        mmc_clk_io_onoff(sdc_no, 1, gpio_info, offset);
        ret = mmc_register(sdc_no, mmc);
        if (ret < 0){
            mmcinfo("mmc %d register failed\n",sdc_no);
            return -1;
        }

        return mmc->lba;
    }

    int mmc_v4px_exit(int sdc_no, const normal_gpio_cfg *gpio_info, int offset)
    {
        mmc_clk_io_onoff(sdc_no, 0, gpio_info, offset);
        mmc_unregister(sdc_no);
        memset(&mmc_dev[sdc_no], 0, sizeof(struct mmc));
        memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host));
        mmcdbg("sunxi mmc%d exit\n",sdc_no);
        return 0;
    }
